using System.Net;
using System.Net.Sockets;
using Pinwheel.Kernel;
using Pinwheel.LSerialize;
using Pinwheel.Service.Protocol;

namespace Pinwheel.Service
{
    public partial class Cluster : IGatewayService
    {
        public void OnConnected(ISocketSession session)
        {
            var endPoint = session.ReceiveEventArgs.RemoteEndPoint as IPEndPoint;
            Assert.Check(endPoint != null, $"[{Node}][{Alias}]OnConnected RemoteEndPoint{session.ReceiveEventArgs.RemoteEndPoint} is not IPEndPoint");
            
            if (!Global.SocketSession2PidMap.TryGetValue(session, out var _))
            {
                var pid = PID.Create();
                Global.SocketSession2PidMap.TryAdd(session, pid);

                var conn = new Connection(ServiceManager, session);
                Global.NodeConnectionMap.TryAdd(pid, conn);
                
                Log.Info($"[{Node}][{Alias}]Connect to {endPoint} success.");
                ServiceManager.Factory.StartNew(() => QueryNodeName(pid, conn));
            }
        }

        public void OnConnectFailed(ISocketSession session)
        {
            Log.Info($"[{Node}][{Alias}]Connect to {session.ReceiveEventArgs.RemoteEndPoint} failed.");
        }
        
        public void OnReceive(ISocketSession session)
        {
            byte[] buffer = null!;
            var size = session.Received.Pop(ref buffer, 0);
            if (size > 0)
            {
                if (Global.SocketSession2PidMap.TryGetValue(session, out var pid))
                {
                    if (Global.NodeConnectionMap.TryGetValue(pid, out var conn))
                    {
                        ServiceManager.Factory.StartNew(() => OnReceiveAsync(conn, pid, buffer));
                        return;
                    }
                }
                Assert.Check(false, $"[{Node}][{Alias}]Drop unknown connection message: {size} bytes");
                Log.Info($"[{Node}][{Alias}]Drop unknown connection message: {size} bytes");
            }
        }
        
        public async Task OnReceiveAsync(IConnection conn, PID source, byte[] buffer)
        {
            var packet = conn.Decode(buffer);
            Assert.Check(packet != default && !string.IsNullOrEmpty(packet.Service),
                $"[{Node}][{Alias}]OnReceiveAsync packet service is null or empty: {packet.Service}");
            var dest = ServiceManager.GetServicePID(packet.Service);
            Assert.Check(dest != PID.Invalid, $"[{Node}{Alias}]OnReceiveAsync service not found: {packet.Service}");
            
            Send(source, dest, ref packet);
        }
        
        public void OnError(ISocketSession session, SocketError error)
        {
            OnClose(session);
        }
        
        public void OnClose(ISocketSession session)
        {
            var endPoint = session.ReceiveEventArgs.RemoteEndPoint as IPEndPoint;
            Assert.Check(endPoint != null, $"[GatewayBase]OnClose RemoteEndPoint{session.ReceiveEventArgs.RemoteEndPoint} is not IPEndPoint");
            
            bool result = Global.SocketSession2PidMap.TryRemove(session, out var pid);
            // Assert.Check(result, $"[{Node}][{Alias}]OnClose has no unauthorized connection: {pid}");
            if (!result) Log.Info($"[{Node}][{Alias}]OnClose has no unauthorized connection: {pid}");
            
            result = Global.NodeConnectionMap.TryRemove(pid, out _);
            // Assert.Check(result, $"[{Node}][{Alias}]OnClose has no unauthorized connection: {pid}");
            if (!result) Log.Info($"[{Node}][{Alias}]OnClose has no unauthorized connection: {pid}");
        }
        
        public async Task QueryNodeName(PID node, Connection conn)
        {
            var message = await Call(m_PID, node, Name, "QueryNodeName", MessageType.Service, Message.Empty);
            Assert.Check(message is NodeInfo, $"[{Node}][{Alias}]QueryNodeName response type not match: {message.GetType()}");
            
            var nodeInfo = (NodeInfo)message;
            nodeInfo.Pid = node;
            
            var result = await Call(PID.System, m_PID, "RegisterNode", MessageType.Service, nodeInfo);
            var success = (Result)result == Result.Success;
            
            if (!success) conn.Close();
        }
    }
}